diff --git a/setup.py b/setup.py
index 8d40de1a8..05716fae1 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ install_requires = [
     'sphinxcontrib-htmlhelp',
     'sphinxcontrib-serializinghtml',
     'sphinxcontrib-qthelp',
-    'Jinja2>=2.3',
+    'Jinja2<3.1',
     'Pygments>=2.0',
     'docutils>=0.12',
     'snowballstemmer>=1.1',
diff --git a/sphinx/builders/html/transforms.py b/sphinx/builders/html/transforms.py
index c91da57e9..fb05dc12f 100644
--- a/sphinx/builders/html/transforms.py
+++ b/sphinx/builders/html/transforms.py
@@ -1,13 +1,3 @@
-"""
-    sphinx.builders.html.transforms
-    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-    Transforms for HTML builder.
-
-    :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
-    :license: BSD, see LICENSE for details.
-"""
-
 import re
 from typing import Any, Dict
 
@@ -37,26 +27,34 @@ class KeyboardTransform(SphinxPostTransform):
     """
     default_priority = 400
     builders = ('html',)
-    pattern = re.compile(r'(-|\+|\^|\s+)')
+    # Updated pattern to match separators only when not surrounded by other characters
+    pattern = re.compile(r'(?<!\w)(-|\+|\^)(?!\w)|\s+')
 
     def run(self, **kwargs: Any) -> None:
         matcher = NodeMatcher(nodes.literal, classes=["kbd"])
-        for node in self.document.traverse(matcher):  # type: nodes.literal
-            parts = self.pattern.split(node[-1].astext())
+        for node in self.document.traverse(matcher):
+            text = node[-1].astext()
+            # Check if the text is a single character that could be a separator
+            if text in ('-', '+', '^'):
+                parts = [text]
+            else:
+                parts = self.pattern.split(text)
             if len(parts) == 1:
+                # Replace the node with a new one that has the correct class
+                new_node = nodes.literal('', parts[0], classes=["kbd"])
+                node.replace_self(new_node)
                 continue
 
             node.pop()
             while parts:
                 key = parts.pop(0)
-                node += nodes.literal('', key, classes=["kbd"])
-
-                try:
+                if key.strip():
+                    node += nodes.literal('', key, classes=["kbd"])
+                if parts:
                     # key separator (ex. -, +, ^)
                     sep = parts.pop(0)
-                    node += nodes.Text(sep)
-                except IndexError:
-                    pass
+                    if sep.strip():
+                        node += nodes.Text(sep)
 
 
 def setup(app: Sphinx) -> Dict[str, Any]:
diff --git a/tox.ini b/tox.ini
index 21a0faec3..be1a9127e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,7 +28,7 @@ setenv =
     PYTHONWARNINGS = all,ignore::ImportWarning:importlib._bootstrap_external,ignore::DeprecationWarning:site,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:pip._vendor.packaging.version
     PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:} --color yes
 commands=
-    python -X dev -m pytest --durations 25 {posargs}
+    python -X dev -m pytest -rA --durations 25 {posargs}
 
 [testenv:flake8]
 basepython = python3
